package com.xuecheng.content.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.content.model.dto.TeachplanMediaDTO;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ex.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {

    @Autowired
    private CourseBaseService courseBaseService;

    @Autowired
    private TeachplanMediaService teachplanMediaService;

    @Autowired
    private MediaApiAgent mediaApiAgent;

    /**
     * 查询课程计划
     *
     * @param courseBaseId 课程id
     * @param companyId    公司id
     * @return 课程计划dto对象
     */
    @Override
    public TeachplanDTO queryTreeNodesByCourseBaseId(Long courseBaseId, Long companyId) {
        //1.是否开启事务
        //查询不用开启事务
        //2.判断关键数据(课程id,机构id)
        if (!ObjectUtil.isAllNotEmpty(courseBaseId, companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        verifyTeachplanCourseMsg(courseBaseId, companyId);
        //6.根据课程id查询课程计划的集合
        TeachplanMapper baseMapper = this.getBaseMapper();
        //7.通过java的递归来生产课程计划的树型机构
        List<TeachplanNode> teachplanNodes = baseMapper.queryTreeNodesByCourseBaseId(courseBaseId);

        //获取父节点
        TeachplanDTO teachplanDTO = null;
        if (!(CollectionUtils.isEmpty(teachplanNodes))) {
            TeachplanNode parentNode = teachplanNodes.remove(0);
            generateTreeNodes(parentNode, teachplanNodes);
            //对课程计划数据进行递归操作生成树形结构数据

            teachplanDTO = TeachplanConvert.INSTANCE.node2dto(parentNode);
        } else {
            teachplanDTO = new TeachplanDTO();
        }

        //8.将数据转化为dto并返回
        return teachplanDTO;
    }

    /**
     * 递归生成树形结构数据
     *
     * @param parentNode     当前的父级
     * @param teachplanNodes 集合数据
     */
    private void generateTreeNodes(TeachplanNode parentNode, List<TeachplanNode> teachplanNodes) {
        if (ObjectUtils.isEmpty(parentNode.getChildrenNodes())) {
            parentNode.setChildrenNodes(new ArrayList<TeachplanNode>());
        }
        for (TeachplanNode teachplanNode : teachplanNodes) {
            if (ObjectUtils.nullSafeEquals(teachplanNode.getParentid(), parentNode.getId())) {
                //如果父节点的id是此节点的父id则添加进父节点中
                parentNode.getChildrenNodes().add(teachplanNode);
                if (!(TeachPlanEnum.THIRD_LEVEL.equals(teachplanNode.getGrade()))) {
                    generateTreeNodes(teachplanNode, teachplanNodes);
                } else {
                    LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(TeachplanMedia::getTeachplanId, teachplanNode.getId());
                    //判断课程计划是否绑定媒资,如果存在就添加
                    TeachplanMedia teachplanMedia = this.teachplanMediaService.getOne(queryWrapper);
                    if (!ObjectUtils.isEmpty(teachplanMedia)) {
                        teachplanNode.setTeachplanMedia(teachplanMedia);
                    }
                }
            }

        }
    }

    private void verifyTeachplanCourseMsg(Long courseBaseId, Long companyId) {
        //3.查询课程计划对应的课程是否存在
        CourseBase courseBase = this.courseBaseService.getById(courseBaseId);
        if (ObjectUtil.isEmpty(courseBase)) {
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }
        //4.判断是否是一家教学机构(如果为空则为运营平台)
        if (!ObjectUtils.isEmpty(companyId)) {
            if (!ObjectUtil.equal(courseBase.getCompanyId(), companyId)) {
                ExceptionCast.cast(CommonErrorCode.E_100108);
            }
        }

        //5.判断课程是否删除
        if (CommonEnum.DELETE_FLAG.getCodeInt().equals(courseBase.getStatus())) {
            ExceptionCast.cast(ContentErrorCode.E_120415);
        }
    }

    /**
     * 创建和修改课程计划信息
     *
     * @param dto       前端传递对象
     * @param companyId 机构id
     * @return 1.判断业务的具体操作
     * 如果id存在,修改
     * 如果id不存在,新增
     * 2.通过数据的特点来完成业务操作
     * 添加课程计划数据(无id值)
     * 修改课程计划数据(有id值)
     * 3.
     */
    @Override
    @Transactional
    public TeachplanDTO createOrmodifyTeachplan(TeachplanDTO dto, Long companyId) {
        //1.判断业务具体操作
        Long teachPlanId = dto.getTeachPlanId();
        TeachplanDTO resultDTO = null;
        if (ObjectUtil.isEmpty(teachPlanId)) {
            //添加课程计划数据
            resultDTO = createTeachPlan(dto, companyId);
        } else {
            //修改课程计划数据
            resultDTO = modifyTeachPlan(dto, companyId);
        }

        return resultDTO;
    }

    //修改课程计划数据

    /**
     * @param dto
     * @param companyId
     * @return 1.判断关键数据
     * 2.判断课程是否存在,
     * 3,修改课程计划数据
     * 4.将修改后的数据转为dto返回前端
     */
    private TeachplanDTO modifyTeachPlan(TeachplanDTO dto, Long companyId) {
        //1.判断关键数据
        if (StringUtil.isBlank(dto.getPname()) || ObjectUtils.isEmpty(companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //判断课程计划是否存在
        Teachplan teachplan = this.getById(dto.getTeachPlanId());
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }
        //2.判断课程计划所对应的课程是否存在
        verifyTeachplanCourseMsg(dto.getCourseId(), companyId);
        Teachplan po = TeachplanConvert.INSTANCE.dto2entity(dto);
        //3.修改课程计划数据 parentId,grade,orderby 不允许修改

        po.setParentid(teachplan.getParentid());
        po.setGrade(teachplan.getGrade());
        po.setOrderby(teachplan.getOrderby());
        boolean result = this.updateById(po);
//        boolean result = this.update(updateWrapper);
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120416);
        }
        // 4.将修改后的数据转为dto返回前端
        TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(po);
        return resultDTO;
    }

    /**
     * @param dto       前端传递的vo转为dto
     * @param companyId 机构id
     *                  <p>
     *                  业务分析:
     *                  1.事务传递()
     *                  2.判断关键数据
     *                  前端页面无法判断关键参数
     *                  由后端数据库决定(子段是否不能为空)
     *                  pname(课程计划名称),parentId,grade,course_id
     *                  3.判断课程计划对应课程是否存在
     *                  查询课程计划对应的课程是否存在
     *                  是否是一家机构
     *                  课程是否删除
     *                  4.添加课程计划
     *                  pname(课程计划名称),前端传递
     *                  parentId,(二级数据后端,三级数据前端
     *                  grade,数据后端
     *                  course_id,前端传递
     *                  5.转换数据po=>dto
     */
    //添加课程计划数据
    private TeachplanDTO createTeachPlan(TeachplanDTO dto, Long companyId) {
        //判断课程计划名称
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120413);
        }
        //判断当前课程计划是否有对应的课程
        if (ObjectUtils.isEmpty(dto.getCourseId())) {
            ExceptionCast.cast(ContentErrorCode.E_120404);
        }
        //  2.判断课程计划对应的课程是否存在
        //判断课程基本信息是否存在
        verifyTeachplanCourseMsg(dto.getCourseId(), companyId);
        //将dto对象转换为po对象
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);
        //3.添加课程计划
        //获取父节点的id==>二级数据后端,三级数据前端
        Teachplan parentNode = getParentNode(dto, companyId);
        //设置等级
        teachplan.setGrade(parentNode.getGrade() + 1);
        //设置排序级别
        Integer orderby = this.baseMapper.queryOrderby(parentNode.getCourseId(), parentNode.getId());
        teachplan.setOrderby(orderby + 1);
        // 保存课程计划数据需要关联父节点数据
        teachplan.setParentid(parentNode.getId());
        boolean result = this.save(teachplan);

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        // 4.转换数据po=>dto
        TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(teachplan);

        return resultDTO;

    }

    /**
     * @param dto       dto对象
     * @param companyId 机构id
     * @return 父节点
     */
    private Teachplan getParentNode(TeachplanDTO dto, Long companyId) {
        // 1.判断课程计划的等级：是二级还是三级
        // 如果parentid是存在的：三级课程计划
        // 如果parentid是不存在的：二级课程计划
        Long parentid = dto.getParentid();
        if (ObjectUtils.isEmpty(dto.getParentid())) {
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getCourseId, dto.getCourseId());
            queryWrapper.eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);
            Teachplan parentNode = this.getOne(queryWrapper);
            if (ObjectUtils.isEmpty(parentNode)) {
                // 根据courseid查询课程基础信息
                CourseBase courseBase = courseBaseService.getById(dto.getCourseId());
                parentNode = new Teachplan();
                parentNode.setParentid(TeachPlanEnum.FIRST_PARENTID_FLAG.longValue());
                parentNode.setPname(courseBase.getName());
                parentNode.setGrade(TeachPlanEnum.FIRST_LEVEL);
                parentNode.setDescription(courseBase.getDescription());
                parentNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                parentNode.setCourseId(dto.getCourseId());

                boolean result = this.save(parentNode);
                if (!result) {
                    ExceptionCast.cast(ContentErrorCode.E_120001);
                }
            }
            return parentNode;
        } else {

            // 三级课程计划父类数据

            Teachplan secNode = this.getById(parentid);

            //- 父级不存在（二级）：抛出异常
            if (ObjectUtils.isEmpty(secNode)) {
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }

            // - 父级存在（二级）：获得父级Id值
            return secNode;

        }
    }

    /**
     * 根据课程计划id删除课程计划
     *
     * @param teachplanId 程计划id
     * @param companyId   机构id
     */
    @Override
    @Transactional
    public void removeTeachplan(Long teachplanId, Long companyId) {
        //1.判断关键数据
        if (!ObjectUtil.isAllNotEmpty(teachplanId, companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.判断课程计划是否已经被删除
        Teachplan teachplanResult = this.getById(teachplanId);
        if (ObjectUtil.isEmpty(teachplanResult)) {
            ExceptionCast.cast(ContentErrorCode.E_120412);
        }

        //3.判断当前课程计划下是否有媒资或者子节点
        //判断媒资信息
        LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TeachplanMedia::getTeachplanId, teachplanId);
        TeachplanMedia mediaResult = this.teachplanMediaService.getOne(queryWrapper);
        if (ObjectUtil.isNotEmpty(mediaResult)) {
            ExceptionCast.cast(ContentErrorCode.E_120414);
        }
        //判断是否有子节点
        LambdaQueryWrapper<Teachplan> queryWrapperTeachplan = new LambdaQueryWrapper<>();
        queryWrapperTeachplan.eq(Teachplan::getParentid, teachplanId);
        Teachplan teachplan = this.getOne(queryWrapperTeachplan);
        if (ObjectUtil.isNotEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120409);
        }
        boolean result = this.removeById(teachplanId);
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

    }

    /**
     * 课程计划绑定媒资信息
     *
     * @param dto       绑定媒资参数
     * @param companyId 机构id
     * @return //业务分析(是否开始事务)
     * 1.判断关键数据
     * teachplanId
     * mediaId
     * companyId
     * <p>
     * 2.判断业务数据
     * 判断媒资信息
     * 判断media数据是否存在
     * 判断是否是同一家机构(上传和绑定都是机构操作)
     * 判断课程计划信息
     * 判断课程计划是否存在
     * 判断是否是同一家机构
     * 判断课程的审核状态
     * 判断课程是否删除
     * 判断课程计划是否为3级
     * 3.课程计划和媒资信息的数据绑定
     * 判断数据绑定信息是否存在(MediaId,TeachPlanId)
     */
    @Override
    @Transactional
    public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
        //1.判断关键数据
        if (ObjectUtils.isEmpty(dto.getMediaId()) ||
                ObjectUtils.isEmpty(dto.getTeachplanId()) ||
                ObjectUtils.isEmpty(companyId)
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        RestResponse<MediaDTO> response = this.mediaApiAgent.getMediaById4s(dto.getMediaId());
        //2.1判断媒资信息是否存在
        if (!response.isSuccessful()) {
            ExceptionCast.castWithCodeAndDesc(response.getCode(), response.getMsg());
        }

        MediaDTO mediaDTO = response.getResult();
        //2.2判断是否是同一家机构
        if (!(ObjectUtils.nullSafeEquals(companyId, mediaDTO.getCompanyId()))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }
        //3.判断课程计划信息
        Teachplan teachplan = this.getById(dto.getTeachplanId());

        //判断课程计划是否存在
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }
        //判断是否是一家机构
        CourseBase courseBase = this.courseBaseService.getById(teachplan.getCourseId());
        if (!(ObjectUtils.nullSafeEquals(companyId, courseBase.getCompanyId()))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }

        //判断课程计划审核状态
        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }

        //判断课程计划是否删除
        if (CommonEnum.DELETE_FLAG.getCodeInt().equals(teachplan.getStatus())) {
            ExceptionCast.cast(ContentErrorCode.E_120412);
        }

        //判断课程计划的等级
        if (!TeachPlanEnum.THIRD_LEVEL.equals(teachplan.getGrade())) {
            ExceptionCast.cast(ContentErrorCode.E_120410);
        }


        //3.课程计划信息和媒资信息的绑定

        LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TeachplanMedia::getTeachplanId, dto.getTeachplanId());
        queryWrapper.eq(TeachplanMedia::getMediaId, dto.getMediaId());

        TeachplanMedia teachplanMedia = this.teachplanMediaService.getOne(queryWrapper);

        if (ObjectUtils.isEmpty(teachplanMedia)) {

            teachplanMedia = new TeachplanMedia();
            teachplanMedia.setTeachplanId(dto.getTeachplanId());
            teachplanMedia.setMediaId(dto.getMediaId());
            teachplanMedia.setCourseId(courseBase.getId());
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());

            boolean result = this.teachplanMediaService.save(teachplanMedia);
            if (!result) {
                ExceptionCast.cast(ContentErrorCode.E_120411);
            }
        } else {
            LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(TeachplanMedia::getMediaId, dto.getMediaId());
            updateWrapper.set(TeachplanMedia::getMediaFilename, dto.getMediaFilename());
            updateWrapper.eq(TeachplanMedia::getId, teachplanMedia.getId());

            boolean result = this.teachplanMediaService.update(updateWrapper);

            if (!result) {
                ExceptionCast.cast(ContentErrorCode.E_120411);
            }
        }
        TeachplanMedia result = this.teachplanMediaService.getById(teachplanMedia.getId());

        TeachplanMediaDTO teachplanMediaDTO = TeachplanConvert.INSTANCE.entity2dto(result);

        return teachplanMediaDTO;
    }
}
